// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
using SnapObjects.Commons;

namespace SnapObjects.Data.SqlServer
{
    internal class SqlServerUpdateSqlGenerator : UpdateSqlGenerator
    {
        public SqlServerUpdateSqlGenerator(SqlServerSqlGenerationHelper sqlServerSqlGenerationHelper) :
            base(sqlServerSqlGenerationHelper)
        {
        }

        public override ResultSetMapping AppendUpdateSql(
            [NotNull] StringBuilder sqlStringBuilder,
            [NotNull] ModelUpdateInfo modelUpdateInfo,
            int sqlPosition)
        {
            Check.NotNull(sqlStringBuilder, nameof(sqlStringBuilder));
            Check.NotNull(modelUpdateInfo, nameof(modelUpdateInfo));

            var name = modelUpdateInfo.Table;
            var schema = modelUpdateInfo.Schema;
            var operations = modelUpdateInfo.ColumnModifications;

            var writeOperations = operations.Where(o => o.IsWrite).ToList();

            if (writeOperations.Count > 0)
            {
                var conditionOperations = operations.Where(o => o.IsCondition).ToList();

                this.AppendUpdateSql(
                    sqlStringBuilder, name, schema, writeOperations, conditionOperations);

                var readOperations = operations.Where(o => o.IsRead).ToList();

                if (readOperations.Count > 0)
                {
                    this.AppendSelectAffectedCountSql(sqlStringBuilder, name, schema, sqlPosition);

                    var keyOperations = operations.Where(o => o.IsKey).ToList();

                    return this.AppendUpdateSelectAffectedSql(
                        sqlStringBuilder, name, schema, readOperations, keyOperations, sqlPosition);
                }
            }

            return this.AppendSelectAffectedCountSql(sqlStringBuilder, name, schema, sqlPosition);
        }

        protected override ResultSetMapping AppendSelectAffectedCountSql(
            StringBuilder sqlStringBuilder, string name, string schema, int sqlPosition)
        {
            sqlStringBuilder
                .Append("SELECT @@ROWCOUNT")
                .Append(this.SqlGenerationHelper.StatementTerminator).AppendLine()
                .AppendLine();

            return ResultSetMapping.RowCountAndLastInResultSet;
        }

        public override void AppendBatchHeader(StringBuilder sqlStringBuilder)
        {
            sqlStringBuilder
                           .Append("SET NOCOUNT ON")
                           .Append(this.SqlGenerationHelper.StatementTerminator).AppendLine();
        }

        protected override void AppendIdentityWhereCondition(StringBuilder sqlStringBuilder, ColumnModification columnModification)
        {
            this.SqlGenerationHelper.DelimitIdentifier(sqlStringBuilder, columnModification.MappedColumn);
            sqlStringBuilder.Append(" = ");

            sqlStringBuilder.Append("scope_identity()");
        }

        protected override void AppendRowsAffectedWhereCondition(StringBuilder sqlStringBuilder, int expectedRowsAffected)
        {
            sqlStringBuilder
                           .Append("@@ROWCOUNT = ")
                           .Append(expectedRowsAffected.ToString(CultureInfo.InvariantCulture));
        }

        private ResultSetMapping AppendUpdateSelectAffectedSql(
            [NotNull] StringBuilder sqlStringBuilder,
            [NotNull] string name,
            [CanBeNull] string schema,
            [NotNull] IReadOnlyList<ColumnModification> readOperations,
            [NotNull] IReadOnlyList<ColumnModification> conditionOperations,
            int sqlPosition)
        {
            Check.NotNull(sqlStringBuilder, nameof(sqlStringBuilder));
            Check.NotEmpty(name, nameof(name));
            Check.NotNull(readOperations, nameof(readOperations));
            Check.NotNull(conditionOperations, nameof(conditionOperations));

            this.AppendSelectHeader(sqlStringBuilder, readOperations);
            this.AppendFromClause(sqlStringBuilder, name, schema);

            this.AppendUpdateSelectWhereAffectedClause(sqlStringBuilder, conditionOperations);

            sqlStringBuilder.Append(this.SqlGenerationHelper.StatementTerminator).AppendLine()
                .AppendLine();

            return ResultSetMapping.RowCountAndNotLastInResultSet;
        }

        private void AppendUpdateSelectWhereAffectedClause(
            [NotNull] StringBuilder sqlStringBuilder,
            [NotNull] IReadOnlyList<ColumnModification> operations)
        {
            Check.NotNull(sqlStringBuilder, nameof(sqlStringBuilder));
            Check.NotNull(operations, nameof(operations));

            sqlStringBuilder.AppendLine().Append("WHERE ");

            this.AppendRowsAffectedWhereCondition(sqlStringBuilder, 1);

            if (operations.Count > 0)
            {
                sqlStringBuilder
                    .Append(" AND ")
                    .AppendValues(
                    operations, (sb, v) => this.AppendWhereCondition(sb, v), " AND ");
            }
        }

        private void AppendWhereCondition(
            StringBuilder sb, ColumnModification columnModification)
        {
            if (columnModification.IsKey || columnModification.IsIdentity)
            {
                if (columnModification.IsRead)
                {
                    this.AppendIdentityWhereCondition(sb, columnModification);
                }
                else
                {
                    this.AppendWhereAffectedCondition(
                        sb, columnModification, columnModification.IsWrite ? false : true);
                }
            }
        }
    }
}
